/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/* 
 * Copyright (c) 2009 City University of Hong Kong
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License m_version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author: Wilson Thong (wilsonwk@ee.cityu.edu.hk) 
 */

#include <sstream>
#include <iostream>
#include <vector>
#include <string>
#include "ns3/drop-tail-queue.h"
#include "bmsn-generator.h"
#include "node-info-manager.h"

using namespace ns3::eecityu;
using namespace ns3;
using namespace std;

namespace ns3 {
namespace eecityu {

NS_LOG_COMPONENT_DEFINE ("BmsnGenerator");
NS_OBJECT_ENSURE_REGISTERED (BmsnGenerator);

/**
 * \brief Present the content of "connectTo" into a string
 */
ostream&
operator<< (ostream& os, const vector<uint32_t>& connectTo)
{
  // enclosing all neighbors within the set syntax
  os << '{';
  // There is no comma in front of the first nodeId and
  // and the flag keeps track if the first nodeId is processed.
  bool isFirst = true;
  // print out each neighbors into a string according to 
  // the format "<nodeId>, <nodeId>, <nodeId>, ..."
  for (vector<uint32_t>::const_iterator nit = connectTo.begin ();
    nit != connectTo.end (); nit++)
  //for (uint32_t n = 0; n < NUM_NBR; n++)
  {
    // obtain the node id, n, from the node iterator, nit
    uint32_t n = *nit;
    // skip to print the next neighbor's nodeId if the current
    // nodeId indicates that it is not to be connected with node i
    if (n == MAX_N_NODES) continue;
    // disallow printing the comma for the first node
    if (!isFirst) os << ", ";
    // print the nodeId of this neighboring node
    os << n;
    // set isFirst flag to false and allow printing a comma
    // in-between nodes
    isFirst = false;
  }
  // enclosing all neighbors within the set syntax
  os << '}' << ends;

  return os;
}

/**
 * \param n1 A node on the connection to be created
 * \param n2 An other node on the connection to be created
 * \param nodes Contain all the nodes of the generated BMSN topology
 * \param devices Contain all the net devices of the generated BMSN topology
 * \param ppHelper Helper object to create a bidirectional link between n1 and n2
 *
 * Connect node n1 with n2 with a point-to-point bidirectional link
 */
void 
Connect (
  uint32_t n1, 
  uint32_t n2, 
  NodeContainer& nodes,
  NetDeviceContainer& devices,
  PointToPointHelper& ppHelper)
{
  NS_LOG_FUNCTION (n1 << n2 << &ppHelper);

  // create a bidirectional links between two nodes indexed by n1 and n2.
  // The method ppHelper.Install returns two net devices representing the 
  // two interfaces so created on the two nodes. These two intefaces
  // are then appended to the NetDeviceContainer devices for later assigning IP addresses.
  //NS_LOG_LOGIC ("Before installing a link, number of devices = " << devices.GetN ());
  devices.Add (ppHelper.Install (nodes.Get (n1), nodes.Get (n2)));
  //NS_LOG_LOGIC ("After installing a link, number of devices  = " << devices.GetN ());
  // print out debug message about which two nodes are just connected together
  //NS_LOG_LOGIC ("linking " << n1 << " and " << n2);
}

/**
 * \brief Add an IPv4 address to a net device
 * \param a1 The first (left-most) octet of an IP address
 * \param a2 The second octet of an IP address
 * \param a3 The third octet of an IP address
 * \param a4 The forth (right-most) octet of an IP address
 * \param nd An net device to be assigned with a IPv4 address
 * \param addrHelper A helper class to assign IP address
 */
void AddIpv4AddressToNetDevice (
  uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4,
  Ptr<NetDevice> nd,
  Ipv4AddressHelper& addrHelper)
{
  NS_LOG_FUNCTION (
    a1 << a2 << a3 << a4 
    << &nd << &addrHelper);

  // align the four octets and form an host IP address
  // 
  // a1 is the first octet and thus it should be shifted right for 24-bit places
  // a2, a3, and a4 are so shifted so that they are on their second, third
  // and forth octet place correctly
  //
  // calculate the Class-A IP address in the 32-bit representation form
  uint32_t uint32addr = (a1 << 24) | (a2 << 16) | (a3 << 8) | a4;
  NS_LOG_LOGIC ("Assigning to the net device an IP address " << Ipv4Address (uint32addr));
  // obtain the host id and create an Ipv4Address object 
  Ipv4Address hostBase ( uint32addr & 0x0 );
  // obtain the network id and create an Ipv4Address object
  Ipv4Address netAddr ( uint32addr & 0xffffffff );
  // set the Class-A address mask
  Ipv4Mask netMask (0xffffffff);
  // Set the host address to the IPv4 address helper
  //NS_LOG_LOGIC ("Ipv4AddressHelper setting: Network Id = " << netAddr 
  //  << ", network mask = " << netMask
  //  << ", host Id = " << hostBase);
  addrHelper.SetBase (
    netAddr, 
    netMask, 
    hostBase
    );
  // assign the host address to the net device
  addrHelper.Assign (nd);
}

/**
 * \brief Assign the four BMSN neighbors to a node
 * 
 * \param i The node to be assigned with four neighbors on a BMSN topology
 * \param nRows The number of rows in the BMSN topology
 * \param connectTo A vector of four node IDs that are the neighbors of the node i
 */
void
AssignBmsnNeighborsTo (uint32_t i, uint32_t nRows, vector<uint32_t>& connectTo)
{
  // total number of nodes in a BMSN topology
  uint32_t nNodes = nRows*nRows;
  // Calculate the base node of the node i
  //
  // Base nodes are the first node of each row in a BMSN topology.
  // Each row is connected as a ring in the "vertical" as well as
  // "horizontal" directions. A vertical ring is found by first locating
  // the base node of a row, and walks vertically for nRows number
  // of nodes and back to the base node. The horizontal ring is found
  // similarly. Thus, the base node of the row, on which node-i is on,
  // must be known
  uint32_t baseNode = i - (i % nRows);
  NS_LOG_LOGIC ("The base node of node-" << i << " is " << baseNode);
  // find out the north neighbor, east neighbor, 
  // south neighbor and west neighbor. Modulus operator
  // is used to ensure nodeIds are within the valid range.
  //
  // i - 1 was amended to be i + nNodes - 1 as the modulus operator
  // may not behave as what expected to be on negative numbers. 
  // As (i + nNodes) MOD nNodes always equal i, i + nodes - 1 is 
  // effectively the same as i - 1 from the prespective of the modulus
  // operation, while at the same time operands of the MOD operator are
  // always positive
  //
  // nbr is an up counter to count from 0. Its usage allows
  // NS_ASSERT() to check for program consistence
  uint32_t nbr = 0;
  connectTo [nbr++] = ( i + nNodes - nRows)       % nNodes;             // north
  connectTo [nbr++] = ((i - baseNode + 1)         % nRows ) + baseNode; // east
  connectTo [nbr++] = ( i + nRows)                % nNodes;             // south
  connectTo [nbr++] = ((i - baseNode + nRows - 1) % nRows ) + baseNode; // west
  // check if the NUM_NBR is set in accordance with the number of neighbors
  // actually programmed above
  NS_ASSERT_MSG (nbr == NUM_NBR, 
    "Number of neighbors (" << nbr 
    << ") and NUM_NBR (" << NUM_NBR 
    << ") are not equal");
}

/**
 * \brief Change the loopback Ipv4 address to the one with 
 * (x, y) coordinate
 *
 * The default loop back address is 127.0.0.1, and it is 
 * the same for all nodes in the simulation. The function
 * change the 2nd and 3rd octets of the loop back address
 * so that the 2nd octet is the x coordinate and the 3rd
 * octet is the y coordinate. I.e., the loop back address
 * becomes 127.x.y.1
 *
 * This change is necessary as packet is first forwarded 
 * to the loop back before accurate being fowarded out of 
 * the node. The makes the packet's source address to be
 * the loop back address, and thus loop back address of
 * all nodes must be different.
 *
 * Forwarding the packet to the loop back interface first
 * because, as of writing this code, ns-3 does not have
 * access to Ptr<Packet> object when packet is first
 * going to be routed out of its source node. The access
 * to Ptr<Packet>, however, is available when nodes receive
 * and incoming packet and preform routing function. As
 * deflection routing in ns-3 is implemented by Tag-based routing,
 * a hack is to forward packets to loop back first and make
 * the packet to be an incoming packet as seen by the source node.
 *
 * \param n The node whose loop back address is to be changed
 * \param x The x coordinate
 * \param y The y coordinate
 * \param addrHelper The helper function to assign IPv4 address. 
 * It can detects IPv4 address assignment errors, such as duplicate
 * addresses.
 */
void
AssignCoordinateToLoopback (
  Ptr<Node> n, 
  uint32_t x, 
  uint32_t y, 
  Ipv4AddressHelper& addrHelper)
{
  NS_LOG_FUNCTION (n << x << y << "addrHelper");
  // create the resulting Ipv4 address
  uint32_t lbAddrBinary = 0;
  lbAddrBinary |= 127 << 8*3;
  lbAddrBinary |= x   << 8*2;
  lbAddrBinary |= y   << 8;
  lbAddrBinary |= 1;
  // get the loop back interface index
  Ptr<Ipv4> ipv4 = n->GetObject<Ipv4> ();
  NS_ASSERT_MSG (ipv4, "Ipv4 protocol object is missing!");
  uint32_t loopbackId = ipv4->GetInterfaceForDevice (n->GetDevice (0));
  // create an IPv4 interface address object for the loop back
  Ipv4InterfaceAddress loopbackAddr = Ipv4InterfaceAddress (
    Ipv4Address (lbAddrBinary), 
    "255.0.0.0"
    );
  // add one more loopback address to the loop back interface
  ipv4->AddAddress (loopbackId, loopbackAddr);
  //// update the static routing table for the new loop back address
  //if (GetStaticRouting () != 0)
  //  GetStaticRouting ()->AddHostRouteTo (loopbackAddr.GetLocal (), loopbackId);
  
  NS_LOG_LOGIC ("Loopback address changed to " << loopbackAddr.GetLocal ());
}

/** 
 * \todo Include m_version number and m_version checking functions
 */
TypeId
BmsnGenerator::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::eecityu::BmsnGenerator")
    .SetParent<Object> ()
    .AddConstructor<BmsnGenerator> ()
    .AddAttribute ("NRows",
                   "Number of nodes in each row of a BMSN. It cannot be 0.",
                   UintegerValue(DEFAULT_N_ROWS),
                   MakeUintegerAccessor (&BmsnGenerator::m_nRows),
                   // the minimum number of nodes in a BMSN must be 4 or more
                   MakeUintegerChecker<uint32_t> (2, MAX_N_NODES))
    .AddAttribute ("NetId",
                   "The fisrt octet of the Class-A network number of the generated BMSN network.",
                   UintegerValue(10),
                   MakeUintegerAccessor (&BmsnGenerator::m_firstOctetNetId),
                   // the minimum number of nodes in a BMSN must be 4 or more
                   MakeUintegerChecker<uint32_t> (1, 127))
    .AddAttribute ("DataRate", 
                   "The default data rate for point to point links",
                   DataRateValue (DataRate ("8000b/s")),
                   MakeDataRateAccessor (&BmsnGenerator::m_bps),
                   MakeDataRateChecker ())
    .AddAttribute ("Delay", 
                   "Transmission delay through the channel",
                   TimeValue (Seconds (0)),
                   MakeTimeAccessor (&BmsnGenerator::m_delay),
                   MakeTimeChecker ())
    .AddAttribute ("MaxPackets", 
                   "The maximum number of packets accepted by this DropTailQueue.",
                   UintegerValue (10),
                   MakeUintegerAccessor (&BmsnGenerator::m_queueMaxPackets),
                   MakeUintegerChecker<uint32_t> ())
    ;

  return tid;
}

BmsnGenerator::BmsnGenerator (void)
{
  NS_LOG_FUNCTION_NOARGS ();
}

void
BmsnGenerator::Generate ()
{
  // Contain all nodes of the BMSN
  NodeContainer nodes;
  // Contain all interfaces/devices of each node in the BMSN
  NetDeviceContainer devices;
  // Generate a BMSN topology
  Generate (nodes, devices);

  NS_LOG_WARN ("No BmsnTopology object is provided and the \
               generated topology is discarded.");
}

void 
BmsnGenerator::Generate ( BmsnTopology& bmsnNet )
{
  // contain all nodes of the BMSN
  NodeContainer nodes;
  // contain all interfaces/devices of each node in the BMSN
  NetDeviceContainer devices;
  // Generate a BMSN topology
  Generate (nodes, devices);
  // update the BmsnTopology object
  bmsnNet.SetNodesAndNetDevices (nodes, devices);
}

void 
BmsnGenerator::Generate ( NodeContainer& nodes, NetDeviceContainer& devices )
{
  NS_LOG_FUNCTION_NOARGS ();

  NS_ASSERT_MSG (nodes.GetN () == 0, "Calling BmsnGenerator::generate () more than once is not allowed.");

  // assist the IP address assignment for the generated BMSN topology
  Ipv4AddressHelper addrHelper;
  // help constructing point to point links between nodes of BMSN.
  // Set all links to the same profile. 
  PointToPointHelper ppHelper;
  ppHelper.SetDeviceAttribute  ("DataRate", DataRateValue (m_bps));
  ppHelper.SetChannelAttribute ("Delay", TimeValue (m_delay));
  ppHelper.SetQueue (
    "ns3::DropTailQueue",
    "Mode", EnumValue (DropTailQueue::PACKETS),
    "MaxPackets", UintegerValue (m_queueMaxPackets)
    );
  // to count the total number of links generated
  uint32_t nLinks = 0;
  // calculate total number of nodes in the BMSN
  uint32_t nNodes = m_nRows * m_nRows;
  NS_ASSERT_MSG (nNodes < MAX_N_NODES, 
    "Number of nodes exceeds MAX_N_NODES. Set MAX_N_NODES to a larger value.")
    ;
  NS_LOG_LOGIC ("Prepare to generate a BMSN with " << nNodes << " nodes.");
  // create all nodes in the BMSN
  nodes.Create (nNodes);	
  // install several Internet protocols to all nodes.
  InternetStackHelper stackHelper;
  stackHelper.Install (nodes);
#if 0 // Debugging. To see if the loopback interface is added after InternetStackHelper::Install ()
      // The answer is yes.
  NS_LOG_LOGIC ("*** Node-0 ***");
  NS_LOG_LOGIC (NodeInfoManager::PrintAllNetDevices (nodes.Get (0)) );
#endif 
  NS_LOG_LOGIC ("Internet protocol stack installed on all BMSN nodes");
  // neighbors of node i in the BMSN
  // nbr[0] = north
  // nbr[1] = east
  // nbr[2] = south
  // nbr[3] = west
  // 
  // The maximum number of neighbors is set by #define NUM_NBR.
  // Changing the program to generate higher dimensions of BMSN
  // must change the NUM_NBR definition accordingly, or the 
  // program may behave unexpectedly.
  //
  // Seting a nodeId to -1 effectively prevent making a connection
  // to the node i.
  //
  // Using vector<uint32_t> intead of uint32_t[] could keep the program
  // self-consistence more elegantly, but may slow down the final simulation
  // program. Thus, arry is used here.
  vector<uint32_t> connectTo;
  connectTo.insert (connectTo.begin (), NUM_NBR, MAX_N_NODES);
  // connect each node to others to form the BMSN
  for (uint32_t i = 0; i < nNodes; i++)
  {
    // calculate the (x, y) coordinate of the node-i in an N-by-N
    // array space, where N is set by m_nRows.
    uint32_t xLocalCoordinate = (i % m_nRows);
    uint32_t yLocalCoordinate = (i / m_nRows);
    NS_LOG_LOGIC ("Considering the local node at (" << xLocalCoordinate
      << ", " << yLocalCoordinate << ") on a " << m_nRows 
      << "-by-" << m_nRows << " grid.");
#if 0 // if set to 1, an additional IP address is added to loopback interface
    // Change the loop back address of this node to the one
    // such that the (x, y) coordinate is encoed into it
    AssignCoordinateToLoopback (
      nodes.Get (i), 
      xLocalCoordinate, 
      yLocalCoordinate, 
      addrHelper);
#endif
    // find out the north neighbor, east neighbor, 
    // south neighbor and west neighbor. 
    AssignBmsnNeighborsTo (i, m_nRows, connectTo);
    NS_LOG_LOGIC ("connected local node " << i << " to " << connectTo);
    // loop for each neighbors and connect the appropriate
    // neighbors to node i
    for (int nbr = 0; nbr < NUM_NBR; nbr++) 
    {
      // connect node i to a neighbor only if the neighbor's nodeId is 
      // larger than or equal to i's nodeId. This is to prevent double-
      // creation of a bidirection channel from node A to B and 
      // later from node B to A again.
      if (connectTo[nbr] >= i)
      {
        // calculate the (x, y) coordinate of the node-i in an N-by-N
        // array space, where N is set by m_nRows.
        uint32_t xNbrCoordinate = (connectTo[nbr] % m_nRows);
        uint32_t yNbrCoordinate = (connectTo[nbr] / m_nRows);
        NS_LOG_LOGIC ("Considering the neigboring node at (" 
          << xNbrCoordinate << ", " << yNbrCoordinate << ") on a " 
          << m_nRows << "-by-" << m_nRows << " grid.");
        // the forth octet of the IP address of the local interface
        //
        // - For north facing interface, nbr == 0 and its IP address
        // must be x.x.x.1
        // - For east facing interface, nbr == 1 and its IP address
        // must be x.x.x.2
        // - For south facing interface, nbr == 2 and its IP address
        // must be x.x.x.3
        // - For west facing interface, nbr == 3 and its IP address
        // must be x.x.x.4
        uint32_t forthOctetLocalInf = nbr + 1;
        // the forth octet of the IP address of the neighboring interface
        //
        // - For the north facing interface, nbr == 0 and the IP address
        // of its neighbor must be x.x.x.3
        // - For the east facing interface, nbr == 1 and the IP address
        // of its neighbor must be x.x.x.4
        // - For the south facing interface, nbr == 2 and the IP address
        // of its neighbor must be x.x.x.1
        // - For the west facing interface, nbr == 3 and the IP address
        // of its neighbor must be x.x.x.2
        uint32_t forthOctetNbrInf = (
          nbr == 0? 3 : (
          nbr == 1? 4 : (
          nbr == 2? 1 : (
          nbr == 3? 2 : 0
          ))));
        NS_ASSERT_MSG (forthOctetNbrInf != 0, "Unexpected error!");
        // count the number of links generated
        nLinks++;
        // make a connection between node i and the node
        // contained in connectTo[nbr]
        Connect (i, connectTo[nbr], nodes, devices, ppHelper);
        // An interator for accessing the last two net devices
        // just created by the BmsnGenerator::Connect () method above
        NetDeviceContainer::Iterator devIter = devices.End ();
        // Assign a correct IP address to the net device
        //
        // The IP address is of the form w.x.y.z, where
        // - w = the first octet of the network address of the generated
        // BMSN network
        // - x = the x coordinate of the node in the N-by-N array space
        // - y = the y coordinate of the node in the N-by-N array space
        // - z = the forth octet of the IP address as defined by 
        // BmsnTopology::IsBmsnTopology ()
        //
        // Now assign IP address to the neighboring net device
        AddIpv4AddressToNetDevice (
          GetFirstOctetNetId (),
          xNbrCoordinate,
          yNbrCoordinate,
          forthOctetNbrInf,
          *(--devIter),
          addrHelper
          );
        // And now assign IP address to the local net device
        AddIpv4AddressToNetDevice (
          GetFirstOctetNetId (),
          xLocalCoordinate,
          yLocalCoordinate,
          forthOctetLocalInf,
          *(--devIter),
          addrHelper
          );
      }
      else // connectTo[nbr] < i
      {
        // set the nodeId to MAX_N_NODES in order to indicate that
        // this node is not to be connected with node i
        connectTo [nbr] = MAX_N_NODES;
      } // if-else block.
    } // for loop block - walk all neighbors of node i  
    //NS_LOG_LOGIC ("connected local node " << i << " to " << connectTo);

#if 1 // debugging. See if the nodes' net device is correctly installed
  NS_LOG_LOGIC ("*** Node-" << i << " ***");
  NS_LOG_LOGIC (NodeInfoManager::PrintAllNetDevices (nodes.Get (i)) );
#endif
  } // for loop back - walk all node of the BMSN
  NS_LOG_LOGIC (devices.GetN () << " net devices created");

  NS_LOG_INFO (
    "Generated a BMSN with " << nNodes 
    << " nodes and " << devices.GetN ()
    << " net devices."
    );
}

uint32_t 
BmsnGenerator::GetFirstOctetNetId ()
{
  return m_firstOctetNetId;
}

} // nacespace eecityu
} // namespace ns3
